<template>
  <div v-if="initialized" class="cloud-filter_bar">
    <template v-for="scheme in schemes?.widgets || []">
      <cloud-filter-input
        v-if="scheme.widget === 'input'"
        :key="scheme.field"
        :model-value="compModelValue(scheme)"
        v-bind="bindValue(scheme)"
        class="cloud-filter_bar-item"
        @change="() => handleChange(scheme)"
        @search="onSearch"
        @update:model-value="
          (value: any) => updateState(value, scheme.field)
        " />
      <cloud-filter-select
        v-if="scheme.widget === 'select'"
        :key="scheme.field"
        class="cloud-filter_bar-item"
        :model-value="compModelValue(scheme)"
        :state="modelValue"
        v-bind="bindValue(scheme)"
        :options="scheme.options"
        :disabled="internalDisabled(scheme)"
        @update:model-value="(value: any) => updateState(value, scheme.field)"
        @change="() => handleChange(scheme)" />
      <!-- @vue-skip -->
      <div
        v-if="scheme.widget === 'strong-search'"
        :key="scheme.field"
        class="flex-1">
        <cloud-filter-strong-search
          class="cloud-filter_bar-item w-auto!"
          :options="scheme.options"
          :model-value="compModelValue(scheme)"
          v-bind="bindValue(scheme)"
          @change="() => handleChange(scheme)"
          @search="updateStrongSearchState" />
      </div>
    </template>
  </div>
</template>

<script setup lang="ts">
import './style.less'

import {
  cloneDeep,
  debounce,
  get,
  isEqual,
  isFunction,
  isString,
  omit,
  omitBy,
  set
} from 'lodash-es'

import type { PromiseFn } from '@/apis/schema/all'
import { getOptionLabel } from '@/utils/format'
import type { SearchValue } from '../strong-search/types'
import type {
  CloudFilterBarScheme,
  CloudFilterBarState,
  CloudFilterBarWidget
} from './types'

// const emit = defineEmits(['change'])
const CloudFilterInput = defineAsyncComponent(
  () => import('./components/InputFilter.vue')
)
const CloudFilterSelect = defineAsyncComponent(
  () => import('./components/SelectFilter.vue')
)
const CloudFilterStrongSearch = defineAsyncComponent(
  () => import('./components/StrongSearchFilter.vue')
)

const schemes = defineModel<CloudFilterBarScheme>('schemes', { local: true })
const modelValue = defineModel<CloudFilterBarState>({
  local: true,
  required: true
})
const handleChange = (scheme: CloudFilterBarWidget) => {
  scheme?.onChange?.(searchState)
}

const internalDisabled = computed<(scheme: CloudFilterBarWidget) => boolean>(
  () => (scheme: CloudFilterBarWidget) => {
    if (isFunction(scheme?.disabled)) {
      // @ts-ignore
      return scheme?.disabled(modelValue.value)
    }
    return false
  }
)

const props = withDefaults(defineProps<{ run?: PromiseFn | (() => any) }>(), {})
const searchState = ref<CloudFilterBarState>({})
const bindValue = computed(() => (scheme: CloudFilterBarWidget) => {
  const omitFields = ['widget', 'field', 'onChange']

  switch (scheme.widget) {
    case 'input':
      return omit(scheme, omitFields)
    case 'select':
      omitFields.push('options')
      return omit(scheme, omitFields)
    case 'strong-search':
      return omit(scheme, omitFields)
    default:
      return {}
  }
})

/** 更新 input select 改变事件 */
const updateState = async (value: string | number, field: string) => {
  set(searchState.value, field, value === '__all__' ? undefined : value)
  await nextTick()
  if (
    isEqualState(toRaw(searchState.value), modelValue.value) ||
    Object.keys(searchState.value).length === 0
  ) {
    return
  }
  updateModelValue()
}

/** 更新 Strong Search 改变事件 */
const updateStrongSearchState = async (
  obj: CloudFilterBarState,
  keys: string[]
) => {
  // 先将所有的keys 对应的值设置为 undefined
  let data = cloneDeep(searchState.value)
  keys.forEach((key) => {
    data[key] = undefined
  })
  // console.log(obj)
  // console.log(keys)
  // 再将新的值设置到对应的key上
  data = { ...data, ...obj }
  searchState.value = data
  await nextTick()
  updateModelValue()
}

const omitUndefined = (value: any) => {
  return omitBy(value, (value) => value === undefined)
}
const isEqualState = (value1: any & {}, value2: any & {}) => {
  return isEqual(omitUndefined(toRaw(value1)), omitUndefined(value2))
}

/** 获取当前组件的ModelValue */
const compModelValue = computed(() => (scheme: CloudFilterBarWidget) => {
  if (scheme?.widget !== 'strong-search') {
    return scheme.field ? modelValue.value?.[scheme.field] : undefined
    // return scheme.field ? searchState.value?.[scheme.field] : undefined
  }
  const keys = scheme.options.map((item) => {
    return {
      field: item.field,
      label: item.label
    }
  })
  const res = keys.reduce((acc, curr) => {
    const val = modelValue.value[curr.field]
    if (val && val !== '__all__') {
      const currFieldOption = scheme.options.find(
        (item) => item.field === curr.field
      )

      let label = val
      if (currFieldOption?.popover === 'select') {
        const opts = toValue(currFieldOption.popoverOption?.select || [])
        label = getOptionLabel(val, opts)
      }

      acc.push({
        options: [{ label: label, value: val }],
        value: [val],
        label: curr.label,
        field: curr.field
      })
    }
    return acc
  }, [] as SearchValue[])
  return res
})

/** 搜索方法 做防抖 */
const onSearch = debounce(() => {
  props?.run?.()
}, 300)

const updateModelValue = async () => {
  const data: Recordable = { ...modelValue.value, ...searchState.value }
  modelValue.value = Object.keys(data).reduce((acc, curr) => {
    let value = data[curr]
    if (isString(data[curr])) {
      value = value.trim()
    }
    acc[curr] = value !== '__all__' ? value : undefined
    return acc
  }, {} as Recordable)
  await nextTick()
  onSearch()
}
const initialized = ref(false)
const reset = () => {
  searchState.value = cloneDeep(modelValue.value)
  const widgets = schemes.value?.widgets || []
  widgets.forEach((curr) => {
    searchState.value[curr.field!] =
      curr.widget === 'select' && !!curr.allText
        ? '__all__'
        : curr.field
          ? get(searchState.value, curr.field!)
          : undefined
  })
}
watch(
  schemes,
  async () => {
    if (!initialized.value) {
      initialized.value = false
    }
    reset()
    if (!initialized.value) {
      initialized.value = true
    }
  },
  { deep: true, immediate: true }
)
defineExpose({ reset })
</script>
